/**
 * Copyright (c) 2007, Blue Hole Software. All rights reserved.
 * Code licensed under the Apache 2.0 License:
 * http://www.apache.org/licenses/
 */
package org.bhf.security.util;

import java.util.Map;
import java.util.NoSuchElementException;

/**
 * Minimalist but fast template substitution for strings.
 * @exclude
 */
public final class Templates
{
    /**
     * Find the length of the plural reference in the template. Only one plural reference is allowed.
     * @param template Template containing substitution markers as ${<key>}.
     * @param context Map of <key> to substitution <value>. <key> must be a String. <value> may be
     *      a String (singular) or String[] (plural).
     * @return 0 if the template has no parameter references, 1 if the template refers to only
     *      singular parameters, and the length of the plural parameter if the template refers
     *      to a plural parameter.
     * @throws RuntimeException The template refers to more than one plural parameter in conext.
     */
    public static int   lengthOfPluralReference( final String template, final Map<String,String[]> context )
        throws RuntimeException
    {
        int length = 0;
        int start  = 0;

        for( int currentIndex; (currentIndex = template.indexOf( "${", start ))> -1; )
        {
            // Is it escaped? ($${)
            if( currentIndex > 0 && template.charAt( currentIndex - 1 ) == '$' )
            {
                start = currentIndex + 1;
            }
            else
            {
                final int end           = closingIndex( template, currentIndex + 2 ); // Start looking after the {
                final String key        = template.substring( currentIndex + 2, end );
                final String[] value    = context.get( key );

                if( value == null || value.length == 0 )
                    throw new RuntimeException( "Key ${" + key + "} not found in context for \"" + template + "\"" );

                // Plural
                if( value.length > 1 )
                {
                    if( length >  1 ) // We found more than one singular reference - nono
                        throw new RuntimeException( "Permission " + template + " refers to more than one plural parameter" );
                    length = value.length;
                }
                // Singular
                else
                {
                    if( length < 1 )
                        length = 1;
                }

                start = end + 1;
            }
        }

        return length;
    }

    /**
     * Performing string substition using a template and context.
     * @param template Template containing substitution markers as ${<key>}.
     * @param context Map of <key> to substitution <value>. <key> must be a String. <value> may be
     *      a String (singular) or String[] (plural).
     * @param parameterIndex Index to be used in selecting one element of a plural parameter.
     * @return Template with applies substitutions.
     * @throws RuntimeException If the template contains a ${} key that is not defined in the provided
     *      context or if an opening ${ is not not closed.
     * @throws java.util.NoSuchElementException If <code>parameterIndex</code> exceeds the number of elements in
     *      a plural parameter.
     */
    public static String process( final String template, final Map<String,String[]> context, final int parameterIndex )
        throws RuntimeException
    {
        final StringBuffer  accumulator     = new StringBuffer( template.length() * 2 );
        final char[]        templateChars   = template.toCharArray(); // Reduces String creation during iterations

        int start = 0;
        for( int currentIndex; (currentIndex = template.indexOf( "${", start ))> -1; )
        {
            // Is it escaped? ($${)
            if( currentIndex > 0 && template.charAt( currentIndex - 1 ) == '$' )
            {
                accumulator.append( templateChars, start, (currentIndex + 1) - start ); // Includes $
                start = currentIndex + 1;
            }
            else
            {
                final int end           = closingIndex( template, currentIndex + 2 ); // Start looking after the {
                final String key        = template.substring( currentIndex + 2, end );
                final String[] value    = context.get( key );

                if( value == null || value.length == 0 )
                    throw new RuntimeException( "Key ${" + key + "} not found in context for \"" + template + "\"" );

                accumulator.append( templateChars, start, currentIndex - start ); // No $

                // Plural
                if( value.length > 1 )
                {
                    if( parameterIndex < value.length )
                    {
                        accumulator.append( value[ parameterIndex ] );
                    }
                    else
                    {
                        throw new NoSuchElementException();
                    }
                }
                // Singular
                else
                {
                    accumulator.append( value[0] );
                }

                start = end + 1;
            }
        }

        // Append remainder
        if( start < template.length() )
            accumulator.append( templateChars, start, template.length() - start );

        return accumulator.toString();
    }

    //
    // Private
    //

    private static int  closingIndex( final String s, final int start )
    {
        int depth = 1;

        for( int i = start; i < s.length(); i++ )
        {
            final char c = s.charAt( i );
            if( c == '{' )
                depth++;
            else if( c == '}' )
                depth--;
            if( depth == 0 )
                return i;
        }

        return -1;
    }
}



